import simplejson
import new

import types
BUILT_IN_TYPES = types.__builtins__.values()

'''
Reason behind this:
1. Want to generate libraries for multiple platforms
2. This means being able to load json into objects for each desired language
3. This also means generating the webcall functions for each desired language
4. That means that each langauge that is added will need the 2nd and 3rd items 
pre-built.
5. Authentication will also me a component that is tricky? How to add OAuth
accross the board. And how to add deal with cookies accross the board. Keep it
simple at first and simply use the HTTPBasic for everything. That means there
won't be a complicated component required for the Auth part of the generated
client library.
6. Each generated API should provide one generated module/class/something 
modular. This API will allow setting of credentials for the specific auth
types (remember just HTTPBasic at first to keep it simple and then OAuth or
cookie based authentication schemes of some sort. Or perhaps some way to drop
in an authentication function of some sort.). 
'''

def json_to_obj(obj_type, json_str):
    '''
    #TODO: this should just take in a jsonobj and not the string because
    when we recurse we don't want to have to call dumps
    #doctests
    
    #simple recursion data structures works
    
    >>> import simplejson
    >>> class Child:
    ...     job = unicode
    ...     number = int
    >>> class TestClass:
    ...     name = unicode
    ...     age = int
    ...     child = Child
    >>> json = '{ "name": "evan", "age": 25 }'
    >>> obj = json_to_obj(TestClass, json)
    >>> obj.name
    u'evan'
    >>> obj.age
    25
    >>> json = '{ "name": "bob", "age": "apple" }'
    >>> json_to_obj(TestClass, json)
    Traceback (most recent call last):
    ...
    TypeError: <type 'int'> != <type 'unicode'>
    >>> json = '{ "name": 123, "age": 123 }'
    >>> json_to_obj(TestClass, json)
    Traceback (most recent call last):
    ...
    TypeError: <type 'unicode'> != <type 'int'>
    >>> json = '{"name":"ann","age":1,"child":{"job":"cook","number":5}}'
    >>> obj = json_to_obj(TestClass, json)
    >>> obj.name
    u'ann'
    >>> obj.age
    1
    >>> obj.child.job
    u'cook'
    >>> obj.child.number
    5
    >>> json = '{"name":"ann","age":1,"child":{"job":1,"number":5}}'
    >>> json_to_obj(TestClass, json)
    Traceback (most recent call last):
    ...
    TypeError: <type 'unicode'> != <type 'int'>
    >>> json = '{"name":"anna","age":6,"child":null}'
    >>> obj = json_to_obj(TestClass, json)
    >>> obj.name
    u'anna'
    >>> obj.age
    6
    >>> obj.child is None
    True
    >>> #begin testing the array types
    >>> json = '[{"job":"winner", "number":12},{"job":"loser","number":61}]'
    >>> child_array = json_to_obj([Child], json)
    >>> len(child_array)
    2
    >>> child_array[0].job
    u'winner'
    >>> child_array[0].number
    12
    >>> child_array[1].job
    u'loser'
    >>> child_array[1].number
    61
    '''
    jsonobj = simplejson.loads(json_str)
    
    #handle the case for list types
    if type(obj_type) is list:
        actual_type = obj_type[0]
        result_list = [json_to_obj(actual_type, simplejson.dumps(i)) for i in jsonobj]
        return result_list
    
    newobj = new.instance(obj_type)

    if jsonobj is None: return None

    for attr in jsonobj:
        if hasattr(newobj, attr):
            #if the type is built in do not recurse on it
            if getattr(obj_type, attr) in BUILT_IN_TYPES:
                if not getattr(obj_type, attr) == type(jsonobj[attr]):
                    raise TypeError(unicode(getattr(obj_type, attr)) + ' != ' +
                        unicode(type(jsonobj[attr])))                
                setattr(newobj, attr, jsonobj[attr])
            else:
                setattr(newobj, attr,
                    json_to_obj(getattr(obj_type, attr), 
                        simplejson.dumps(jsonobj[attr])))

    return newobj

if __name__ == '__main__':
    import doctest
    doctest.testmod()

